#include "Precomp.h"
#include "Map.h"
#include "Entity.h"
#include "Tile.h"
#include "Renderer.h"
#include "Utils.h"
#include "SquirrelHelper.h"
#include <boost/bind.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/construct.hpp>
#include <boost/lambda/bind.hpp>
#include <PhysFS++.h>
#include "AnimatedSprite.h"
#include "Logger.h"

using namespace boost;

Map* Map::defaultMap=NULL;

Map::Map()
{
	addLayer(L"Default");
	addLayer(L"Overlay");
	addLayer(L"Overlay2");
	addLayer(L"Overlay3");
	addLayer(L"Foreground");
	addLayer(L"Foreground2");
	addLayer(L"Foreground3");
	addLayer(L"Entity");
	addLayer(L"Entity2");
	addLayer(L"Background");
	addLayer(L"Background2");
	addLayer(L"Background3");
}

Map::~Map()
{
	std::for_each(layers.begin(),layers.end(),lambda::bind(lambda::delete_ptr(), lambda::_1));//delete all layers
	std::for_each(entities.begin(),entities.end(),lambda::bind(lambda::delete_ptr(), lambda::_1));//delete all entity
}

void Map::addLayer(const SQChar* name)
{
	layers.push_back(new Layer(name));
}

void Map::addEntity(Entity* entity)
{
	entities.push_back(entity);
	entity->connections[Entity::UPDATE]=updateSignal.connect(boost::bind(&Entity::update,entity),*entity);
}

void Map::addAnimatedSprite(AnimatedSprite* anim)
{
	anim->connections[Entity::ANIMATE]=animateSignal.connect(boost::bind(&AnimatedSprite::animate,anim),*anim);
}

void Map::removeEntity(Entity* entity)
{
	entities.remove(entity);
	entity->connections[Entity::UPDATE].disconnect();
	if(entity->getType()==Entity::ANIMATED)
	{
		AnimatedSprite* anim=static_cast<AnimatedSprite*>(entity);
		anim->connections[Entity::ANIMATE].disconnect();
	}
}

void Map::onEnter()
{
}

void Map::onLeave()
{
}

void Map::update()
{
	animateSignal();
	updateSignal();
}

Layer* Map::getLayerByName(const SQChar* name)
{
	for(std::vector<Layer*>::iterator i=layers.begin();i!=layers.end();++i)
	{
		if(!wcscmp((*i)->getName(),name))//found
			return *i;
	}
	return 0;
}

Layer* Map::getLayerByIndex(unsigned int index)
{
	return layers[index];
}


Entity* Map::getEntityAt(const sf::Vector2f& position) const
{
	for(std::list<Entity*>::const_iterator i=entities.begin();i!=entities.end();++i)
	{
		sf::Matrix3 matrix;//TODO: optimize with TransformToGlobal and TransformToLocal
		sf::Drawable* shape=(*i)->shape;
		matrix.SetFromTransformations(shape->GetCenter(),shape->GetPosition(),shape->GetRotation(),sf::Vector2f(1,1));
		sf::Vector2f size=(*i)->size;
		size.x*=shape->GetScale().x;
		size.y*=shape->GetScale().y;
		if(isPointInBox(size,matrix,position))
		{
			return *i;
		}
	}
	return NULL;
}

void objToTable(SquirrelObject& table,const mapformat::Object& obj)
{
	int count=obj.objectdata_size();
	static wstring key;
	for(int i=0;i<count;++i)
	{
		const mapformat::Data& data=obj.objectdata(i);
		key=toWCS(data.key().c_str());
		switch(data.datatype())
		{
		case mapformat::Data::STRING:
			table.SetValue(key.c_str(),toWCS(data.svalue().c_str()).c_str());
		break;
		case mapformat::Data::FLOAT:
			table.SetValue(key.c_str(),data.fvalue());
		break;
		case mapformat::Data::INT:
			table.SetValue(key.c_str(),data.ivalue());
		break;
		case mapformat::Data::BOOL:
			table.SetValue(key.c_str(),data.bvalue());
		break;
		}
	}
}

void Map::reload()
{
	//clean up the map before re-loading
	std::for_each(entities.begin(),entities.end(),lambda::bind(lambda::delete_ptr(),lambda::_1));
	updateSignal.disconnect_all_slots();
	animateSignal.disconnect_all_slots();
	entities.clear();
	std::for_each(layers.begin(),layers.end(),lambda::bind(&Layer::clear,lambda::_1));
	
	loadFromFile(name.c_str());
}

bool Map::loadFromFile(const SQChar* path)
{
	name=path;
	mapformat::Map map;//our google's protocol buffer
	Map* defaultMap=Map::defaultMap;
	Map::defaultMap=this;//point new objects to this map
	PhysFS::ifstream file(toUTF8(path).c_str());
	map.ParseFromIstream(&file);//get the map from file
	file.close();
	int count=map.objects_size();//count the number of objecFts
	for(int i=0;i<count;++i)
	{
		const mapformat::Object& obj=map.objects(i);
		const std::string& type=obj.type();//get object type
		if(type=="Tile")//if this is a tile, we do it with C++
		{//TODO: optimize
			SquirrelObject table=SquirrelVM::CreateTable();
			objToTable(table,obj);
			Tile* tile=new Tile(table.GetString(L"path"));
			tile->deserialize(table);
			tile->gotoMap(this);
			continue;
		}//else we let Squirrel handle the object*/
		SquirrelObject Class=SquirrelVM::GetRootTable().GetValue(toWCS(type.c_str()).c_str());//get the class
		if(Class.GetType()!=OT_CLASS)
		{
			Log("Class %s does not exist. Do not overwrite the class.",type.c_str());
			continue;
		}
		HSQUIRRELVM vm=SquirrelVM::GetVMPtr();
		SquirrelObject table=SquirrelVM::CreateTable();
		SquirrelObject instance;
		//int top=sq_gettop(vm);
		sq_pushobject(vm,Class);
		sq_pushroottable(vm);		
		if(SQ_SUCCEEDED(sq_call(vm,1,true,true)))
		{
			instance.AttachToStackObject(-1);
			sq_pop(vm,2);//pop the instance and the class
			//assert(top==sq_gettop(vm));
		}
		else
		{
			sq_pop(vm,1);//pop the class
			//assert(top==sq_gettop(vm));
			throw SquirrelError();
		}
		Entity* entity=instance.Get<Entity*>();
		objToTable(table,obj);//convert protocol buffer to table
		entity->deserialize(table);//start deserializing
		//entity->gotoMap(this);
	}
	Map::defaultMap=defaultMap;
	return true;
}

void tableToObj(SquirrelObject& table,mapformat::Object& obj)
{
	static mapformat::Data data;
	static SquirrelObject key,val;
	table.BeginIteration();	
	while(table.Next(key,val))
	{
		data.Clear();
		bool supported=true;
		if(key.GetType()!=OT_STRING)//only support string key
			continue;
		switch(val.GetType())
		{
		case OT_STRING:
		{
			data.set_datatype(mapformat::Data::STRING);
			data.set_svalue(toUTF8(val.ToString()));
		}
		break;
		case OT_BOOL:
			data.set_datatype(mapformat::Data::BOOL);
			data.set_bvalue(val.ToBool());
		break;
		case OT_INTEGER:
			data.set_datatype(mapformat::Data::INT);
			data.set_ivalue(val.ToInteger());
		break;
		case OT_FLOAT:
			data.set_datatype(mapformat::Data::FLOAT);
			data.set_fvalue(val.ToFloat());
		break;
		default:
			Log("Unsupported data type\n");
			supported=false;
		break;
		}
		if(supported)
		{
			data.set_key(toUTF8(key.ToString()));
			obj.add_objectdata()->CopyFrom(data);
		}
	}
	table.EndIteration();
	key.Reset();val.Reset();
}

void Map::saveToFile(const SQChar* path)
{
	name=path;
	mapformat::Map map;
	SquirrelObject out,key,val;//TODO: optimize
	for(std::list<Entity*>::iterator i=entities.begin();i!=entities.end();++i)
	{
		out=SquirrelVM::CreateTable();//create a table for the entity
		(*i)->serialize(out);//let it serialize the data
		//convert table to google's protocol buffer;
		if(out.GetValue(L"type").IsNull())
			continue;//ignore this object if the field type is not set
		mapformat::Object* obj=map.add_objects();
		obj->set_type(toUTF8(out.GetString(L"type")));
		tableToObj(out,*obj);
	}
	PhysFS::ofstream file(toUTF8(path).c_str());
	map.SerializeToOstream(&file);
	file.flush();
	file.close();
}